home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PsL Monthly 1994 December
/
PSL Monthly Shareware CD-ROM (Public Software Library)(December 1994).bin
/
prgmming
/
dos
/
c3
/
gp.c
< prev
next >
Wrap
C/C++ Source or Header
|
1992-09-06
|
12KB
|
340 lines
/* A General Reentrant printf() Function
by Philip J. Erdelsky
CompuServe 75746,3411
InterNet 75746.3411@compuserve.com
June 8, 1992
PUBLIC DOMAIN -- NO RESTRICTIONS ON USE
The function printf() and its companions fprintf(), sprintf(), vprintf(), etc.,
are found in every standard C library and are called by nearly every C
application. However, without source code they are rather difficult to adapt
to environments other than those for which they were written, and they are not
necessarily reentrant.
Our version of these functions, which we call general_printf(), is as portable
and versatile as we could make it, and it is completely reentrant. You may
have to serialize access to the output device, but you won't have to serialize
access to general_printf() itself.
We don't support floating-point editing, but we do support other features of
the original printf() and also some add-ons. The editing phrase %b edits an
integer in binary, and the phrase %u edits an unsigned integer in decimal.
Both permit the standard field parameter and the modifier l if the integer is
long. An asterisk, if used as a field or precision parameter, takes on the
value of the corresponding argument, which must be an integer. The editing
phrase %x uses small letters in the edited result, the editing phrase %X uses
capital letters.
We tacitly assume that function arguments of type "short" and "char" are
expanded to type "int", and that the size of an argument of type "long" or
"char *" is a multiple of the size of an argument of type "int". These
assumptions are true for all compilers that we are familiar with.
The call on general_printf() is as follows:
n = general_printf(output_function, output_pointer, control_string,
argument_pointer);
void (*output_function)(void *, int); function to be called to output
a character of the edited result
void *output_pointer; pointer to be passed to
(*output_function)()
char *control_string; control string, also called a
format string
int *argument_pointer; pointer to first argument in
argument list
int n; number of characters sent to
output device, or a negative
error code
The function uses the control string to edit the argument list and sends the
resulting string of ASCII characters to the output device, one at a time, by
calling the function (*output_function)() as follows:
e = (*output_function)(output_pointer, c);
void *output_pointer; implementation-defined pointer
int c; character of edited string
int e; nonnegative value for successful operation,
or negative error code
The pointer may be a file pointer, a drive number, or a pointer to some
implementation-defined descriptor block. The function general_printf() makes
no assumptions about it, but merely passes it on. The function need not
return the character c if the operation is successful, but it must return a
negative value if there was an error. This value is then returned unchanged
by general_printf() as its functional value.
The function general_printf() need not be called directly. You may find it
much more convenient to use it as a basis for your own versions of the standard
C library functions. Here are examples of possible alternative versions of
fprintf() and sprintf() under DOS:
#include <stdio.h>
static int output(void *fp, int c)
{
return putc(c, (FILE *) fp);
}
int alternative_fprintf(FILE *fp, char *control, ...)
{
return general_printf(output, fp, control, (int *)(&control+1));
}
static int fill_string(void *p, int c)
{
*(*(char **)p)++ = c;
return 0;
}
int alternative_sprintf(char *s, char *control, ...)
{
int n = general_printf(fill_string, &s, control, (int *)(&control+1));
*s = 0;
return n;
}
The implementation of general_printf() uses "short" arithmetic in a few places
where "int" arithmetic might have been used. Of course, if the two types are
the same, there is no harm in doing this; but if "short" variables occupy two
bytes and "int" variables occupy four bytes, some stack space is saved by using
"short" variables. However, there is also a much better reason. If
general_printf() is given corrupt input, it may hang up in one of its internal
loops. If the loop counter is four bytes long, the system or process may
effectively freeze because executing even a tight loop over 2 billion times
will take longer than the user is prepared to wait. If the loop counter is
only two bytes long, it will finish after at most 32,767 iterations.
-----------------------------------------------------------------------------*/
#define BITS_PER_BYTE 8
struct parameters
{
int number_of_output_chars;
short minimum_field_width;
char options;
#define MINUS_SIGN 1
#define RIGHT_JUSTIFY 2
#define ZERO_PAD 4
#define CAPITAL_HEX 8
short edited_string_length;
short leading_zeros;
int (*output_function)(void *, int);
void *output_pointer;
};
static void output_and_count(struct parameters *p, int c)
{
if (p->number_of_output_chars >= 0)
{
int n = (*p->output_function)(p->output_pointer, c);
if (n>=0) p->number_of_output_chars++;
else p->number_of_output_chars = n;
}
}
static void output_field(struct parameters *p, char *s)
{
short justification_length =
p->minimum_field_width - p->leading_zeros - p->edited_string_length;
if (p->options & MINUS_SIGN)
{
if (p->options & ZERO_PAD)
output_and_count(p, '-');
justification_length--;
}
if (p->options & RIGHT_JUSTIFY)
while (--justification_length >= 0)
output_and_count(p, p->options & ZERO_PAD ? '0' : ' ');
if (p->options & MINUS_SIGN && !(p->options & ZERO_PAD))
output_and_count(p, '-');
while (--p->leading_zeros >= 0)
output_and_count(p, '0');
while (--p->edited_string_length >= 0)
output_and_count(p, *s++);
while (--justification_length >= 0)
output_and_count(p, ' ');
}
int general_printf(int (*output_function)(void *, int), void *output_pointer,
char *control_string, int *argument_pointer)
{
struct parameters p;
char control_char;
p.number_of_output_chars = 0;
p.output_function = output_function;
p.output_pointer = output_pointer;
control_char = *control_string++;
while (control_char != '\0')
{
if (control_char == '%')
{
short precision = -1;
short long_argument = 0;
short base = 0;
control_char = *control_string++;
p.minimum_field_width = 0;
p.leading_zeros = 0;
p.options = RIGHT_JUSTIFY;
if (control_char == '-')
{
p.options = 0;
control_char = *control_string++;
}
if (control_char == '0')
{
p.options |= ZERO_PAD;
control_char = *control_string++;
}
if (control_char == '*')
{
p.minimum_field_width = *argument_pointer++;
control_char = *control_string++;
}
else
{
while ('0' <= control_char && control_char <= '9')
{
p.minimum_field_width =
p.minimum_field_width * 10 + control_char - '0';
control_char = *control_string++;
}
}
if (control_char == '.')
{
control_char = *control_string++;
if (control_char == '*')
{
precision = *argument_pointer++;
control_char = *control_string++;